<template>
	<view v-if="show" class="tn-sign-board-class tn-sign-board"
		:style="{top: `${customBarHeight}px`, height: `calc(100% - ${customBarHeight}px)`}">
		<!-- 签名canvas -->
		<view class="tn-sign-board__content">
			<view class="tn-sign-board__content__wrapper">
				<canvas class="tn-sign-board__content__canvas" :canvas-id="canvasName" :disableScroll="true"
					@touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></canvas>
			</view>
		</view>

		<!-- 底部工具栏 -->
		<view class="tn-sign-board__tools">
			<!-- 可选颜色 -->
			<view class="tn-sign-board__tools__color">
				<view v-for="(item, index) in signSelectColor" :key="index" class="tn-sign-board__tools__color__item"
					:class="[{'tn-sign-board__tools__color__item--active': currentSelectColor === item}]"
					:style="{backgroundColor: item}" @tap="colorSwitch(item)"></view>
			</view>

			<!-- 按钮 -->
			<view class="tn-sign-board__tools__button">
				<view class="tn-sign-board__tools__button__item tn-bg-red" @tap="reDraw">清除</view>
				<view class="tn-sign-board__tools__button__item tn-bg-blue" @tap="save">保存</view>
				<view class="tn-sign-board__tools__button__item tn-bg-indigo" @tap="previewImage">预览</view>
				<view class="tn-sign-board__tools__button__item tn-bg-orange" @tap="closeBoard">关闭</view>
			</view>
		</view>

		<!-- 伪全屏生成旋转图片canvas容器，不在页面上展示 -->
		<view style="position: fixed; left: -2000px;width: 0;height: 0;overflow: hidden;">
			<canvas canvas-id="temp-tn-sign-canvas"
				:style="{width: `${canvasHeight}px`, height: `${canvasHeight}px`}"></canvas>
		</view>
	</view>
</template>

<script>
	export default {
		name: 'tn-sign-board',
		props: {
			// 是否显示
			show: {
				type: Boolean,
				default: false
			},
			// 可选签名颜色
			signSelectColor: {
				type: Array,
				default () {
					return ['#080808', '#E83A30']
				}
			},
			// 是否旋转输出图片
			rotate: {
				type: Boolean,
				default: true
			},
			// 自定义顶栏的高度
			customBarHeight: {
				type: [String, Number],
				default: 0
			}
		},
		data() {
			return {
				canvasName: 'tn-sign-canvas',
				ctx: null,
				canvasWidth: 0,
				canvasHeight: 0,
				currentSelectColor: this.signSelectColor[0],
				// 第一次触摸
				firstTouch: false,
				// 透明度
				transparent: 1,
				// 笔迹倍数
				lineSize: 1.5,
				// 最小画笔半径
				minLine: 0.5,
				// 最大画笔半径
				maxLine: 4,
				// 画笔压力
				pressure: 1,
				// 顺滑度，用60的距离来计算速度
				smoothness: 60,
				// 当前触摸的点
				currentPoint: {},
				// 当前线条
				currentLine: [],
				// 画笔圆半径
				radius: 1,
				// 裁剪区域
				cutArea: {
					top: 0,
					right: 0,
					bottom: 0,
					left: 0
				},
				// 所有线条， 生成贝塞尔点
				// bethelPoint: [],
				// 上一个点
				lastPoint: 0,
				// 笔迹
				chirography: [],
				// 当前笔迹
				// currentChirography: {},
				// 画线轨迹，生成线条的实际点
				linePrack: [],
				signature:1,
			}
		},
		watch: {
			show(value) {
				if (value && this.canvasWidth === 0 && this.canvasHeight === 0) {
					this.$nextTick(() => {
						this.getCanvasInfo()
					})
				}
			},
			signSelectColor(value) {
				if (value.length > 0) {
					this.currentSelectColor = value[0]
				}
			}
		},
		created() {
			// 创建canvas
			this.ctx = uni.createCanvasContext(this.canvasName, this)
		},
		mounted() {
			// 获取画板的相关信息
			// this.$nextTick(() => {
			//   this.getCanvasInfo()
			// })
		},
		methods: {
			// 获取画板的相关信息
			getCanvasInfo() {
				this._tGetRect('.tn-sign-board__content__canvas').then(res => {
					this.canvasWidth = res.width
					this.canvasHeight = res.height

					// 初始化Canvas
					this.$nextTick(() => {
						this.initCanvas('#FFFFFF')
					})
				})
			},
			// 初始化Canvas
			initCanvas(color) {
				/* 将canvas背景设置为 白底，不设置  导出的canvas的背景为透明 */
				// rect() 参数说明  矩形路径左上角的横坐标，左上角的纵坐标, 矩形路径的宽度, 矩形路径的高度
				// 矩形的宽高需要减去边框的宽度
				this.ctx.rect(0, 0, this.canvasWidth - uni.upx2px(4), this.canvasHeight - uni.upx2px(4))
				this.ctx.setFillStyle(color)
				this.ctx.fill()
				this.ctx.draw()
			},
			// 开始画
			onTouchStart(e) {
				if (e.type != 'touchstart') return false
				console.log("this.signature",this.signature)
				// 设置线条颜色
				this.ctx.setFillStyle(this.currentSelectColor)
				// 设置透明度
				this.ctx.setGlobalAlpha(this.transparent)
				let currentPoint = {
					x: e.touches[0].x,
					y: e.touches[0].y
				}
				let currentLine = this.currentLine
				currentLine.unshift({
					time: new Date().getTime(),
					dis: 0,
					x: currentPoint.x,
					y: currentPoint.y
				})
				this.currentPoint = currentPoint
				if (this.firstTouch) {
					this.cutArea = {
						top: currentPoint.y,
						right: currentPoint.x,
						bottom: currentPoint.y,
						left: currentPoint.x
					}
					this.firstTouch = false
				}

				this.pointToLine(currentLine)
			},
			// 正在画
			onTouchMove(e) {
				this.signature=this.signature+1;
				console.log("this.signature正在画",this.signature)
				if (e.type != 'touchmove') return false
				if (e.cancelable) {
					// 判断默认行为是否已经被禁用
					if (!e.defaultPrevented) {
						e.preventDefault()
					}
				}
				let point = {
					x: e.touches[0].x,
					y: e.touches[0].y
				}

				if (point.y < this.cutArea.top) {
					this.cutArea.top = point.y
				}
				if (point.y < 0) this.cutArea.top = 0

				if (point.x < this.cutArea.right) {
					this.cutArea.right = point.x
				}
				if (this.canvasWidth - point.x <= 0) {
					this.cutArea.right = this.canvasWidth
				}
				if (point.y > this.cutArea.bottom) {
					this.cutArea.bottom = this.canvasHeight
				}
				if (this.canvasHeight - point.y <= 0) {
					this.cutArea.bottom = this.canvasHeight
				}
				if (point.x < this.cutArea.left) {
					this.cutArea.left = point.x
				}
				if (point.x < 0) this.cutArea.left = 0

				this.lastPoint = this.currentPoint
				this.currentPoint = point

				let currentLine = this.currentLine
				currentLine.unshift({
					time: new Date().getTime(),
					dis: this.distance(this.currentPoint, this.lastPoint),
					x: point.x,
					y: point.y
				})

				this.pointToLine(currentLine)
			},
			// 移动结束
			onTouchEnd(e) {
				console.log("this.signature移动结束",this.signature)
				if (e.type != 'touchend') return false
				let point = {
					x: e.changedTouches[0].x,
					y: e.changedTouches[0].y
				}
				this.lastPoint = this.currentPoint
				this.currentPoint = point

				let currentLine = this.currentLine
				currentLine.unshift({
					time: new Date().getTime(),
					dis: this.distance(this.currentPoint, this.lastPoint),
					x: point.x,
					y: point.y
				})

				//一笔结束，保存笔迹的坐标点，清空，当前笔迹
				//增加判断是否在手写区域
				this.pointToLine(currentLine)
				let currentChirography = {
					lineSize: this.lineSize,
					lineColor: this.currentSelectColor
				}

				let chirography = this.chirography
				chirography.unshift(currentChirography)
				this.chirography = chirography

				let linePrack = this.linePrack
				linePrack.unshift(this.currentLine)
				this.linePrack = linePrack
				this.currentLine = []
			},
			// 重置绘画板
			reDraw() {
				this.signature=1;
				this.initCanvas('#FFFFFF')
			},
			// 保存
			save() {
				console.log("this.signature保存",this.signature)
				if(this.signature<=5){
					this.$tn.message.toast('请签名')
					return;
				}
				// 在组件内使用需要第二个参数this
				uni.canvasToTempFilePath({
					canvasId: this.canvasName,
					fileType: 'png',
					quality: 1,
					success: (res) => {
						console.log("save()",res)
						if (this.rotate) {
							this.getRotateImage(res.tempFilePath).then((res) => {
								this.$emit('save', res)
							}).catch(err => {
								this.$tn.message.toast('旋转图片失败')
							})
						} else {
							this.$emit('save', res.tempFilePath)
						}
					},
					fail: () => {
						this.$tn.message.toast('保存失败')
					}
				}, this)
			},
			// 预览图片
			previewImage() {
				// 在组件内使用需要第二个参数this
				uni.canvasToTempFilePath({
					canvasId: this.canvasName,
					fileType: 'png',
					quality: 1,
					success: (res) => {
						if (this.rotate) {
							this.getRotateImage(res.tempFilePath).then((res) => {
								uni.previewImage({
									urls: [res]
								})
							}).catch(err => {
								this.$tn.message.toast('旋转图片失败')
							})
						} else {
							uni.previewImage({
								urls: [res.tempFilePath]
							})
						}
					},
					fail: (e) => {
						this.$tn.message.toast('预览失败')
					}
				}, this)
			},
			// 关闭签名板
			closeBoard() {
				this.$tn.message.modal('提示信息', '关闭后内容将被清除，是否确认关闭', () => {
					this.$emit('closed')
				}, true)
			},
			// 切换画笔颜色
			colorSwitch(color) {
				this.currentSelectColor = color
			},
			// 绘制两点之间的线条
			pointToLine(line) {
				this.calcBethelLine(line)
			},
			// 计算插值，让线条更加圆滑
			calcBethelLine(line) {
				if (line.length <= 1) {
					line[0].r = this.radius
					return
				}
				let x0,
					x1,
					x2,
					y0,
					y1,
					y2,
					r0,
					r1,
					r2,
					len,
					lastRadius,
					dis = 0,
					time = 0,
					curveValue = 0.5;
				if (line.length <= 2) {
					x0 = line[1].x
					y0 = line[1].y
					x2 = line[1].x + (line[0].x - line[1].x) * curveValue
					y2 = line[1].y + (line[0].y - line[1].y) * curveValue
					x1 = x0 + (x2 - x0) * curveValue
					y1 = y0 + (y2 - y0) * curveValue
				} else {
					x0 = line[2].x + (line[1].x - line[2].x) * curveValue
					y0 = line[2].y + (line[1].y - line[2].y) * curveValue
					x1 = line[1].x
					y1 = line[1].y
					x2 = x1 + (line[0].x - x1) * curveValue
					y2 = y1 + (line[0].y - y1) * curveValue
				}
				// 三个点分别是(x0,y0),(x1,y1),(x2,y2) ；(x1,y1)这个是控制点，控制点不会落在曲线上；实际上，这个点还会手写获取的实际点，却落在曲线上
				len = this.distance({
					x: x2,
					y: y2
				}, {
					x: x0,
					y: y0
				})
				lastRadius = this.radius
				for (let i = 0; i < line.length - 1; i++) {
					dis += line[i].dis
					time += line[i].time - line[i + 1].time
					if (dis > this.smoothness) break
				}

				this.radius = Math.min((time / len) * this.pressure + this.minLine, this.maxLine) * this.lineSize
				line[0].r = this.radius
				// 计算笔迹半径
				if (line.length <= 2) {
					r0 = (lastRadius + this.radius) / 2
					r1 = r0
					r2 = r1
				} else {
					r0 = (line[2].r + line[1].r) / 2
					r1 = line[1].r
					r2 = (line[1].r + line[0].r) / 2
				}
				let n = 5
				let point = []
				for (let i = 0; i < n; i++) {
					let t = i / (n - 1)
					let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2
					let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2
					let r = lastRadius + ((this.radius - lastRadius) / n) * i
					point.push({
						x,
						y,
						r
					})
					if (point.length === 3) {
						let a = this.ctaCalc(point[0].x, point[0].y, point[0].r, point[1].x, point[1].y, point[1].r, point[
							2].x, point[2].y, point[2].r)
						a[0].color = this.currentSelectColor

						this.drawBethel(a, true)
						point = [{
							x,
							y,
							r
						}]
					}
				}
				this.currentLine = line
			},
			// 求两点之间的距离
			distance(a, b) {
				let x = b.x - a.x
				let y = b.y - a.y
				return Math.sqrt(x * x + y * y)
			},
			// 计算点信息
			ctaCalc(x0, y0, r0, x1, y1, r1, x2, y2, r2) {
				let a = [],
					vx01,
					vy01,
					norm,
					n_x0,
					n_y0,
					vx21,
					vy21,
					n_x2,
					n_y2;
				vx01 = x1 - x0
				vy01 = y1 - y0
				norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2
				vx01 = (vx01 / norm) * r0
				vy01 = (vy01 / norm) * r0
				n_x0 = vy01
				n_y0 = -vx01
				vx21 = x1 - x2
				vy21 = y1 - y2
				norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2
				vx21 = (vx21 / norm) * r2
				vy21 = (vy21 / norm) * r2
				n_x2 = -vy21
				n_y2 = vx21
				a.push({
					mx: x0 + n_x0,
					my: y0 + n_y0,
					color: '#080808'
				})
				a.push({
					c1x: x1 + n_x0,
					c1y: y1 + n_y0,
					c2x: x1 + n_x2,
					c2y: y1 + n_y2,
					ex: x2 + n_x2,
					ey: y2 + n_y2
				})
				a.push({
					c1x: x2 + n_x2 - vx21,
					c1y: y2 + n_y2 - vy21,
					c2x: x2 - n_x2 - vx21,
					c2y: y2 - n_y2 - vy21,
					ex: x2 - n_x2,
					ey: y2 - n_y2
				})
				a.push({
					c1x: x1 - n_x2,
					c1y: y1 - n_y2,
					c2x: x1 - n_x0,
					c2y: y1 - n_y0,
					ex: x0 - n_x0,
					ey: y0 - n_y0
				})
				a.push({
					c1x: x0 - n_x0 - vx01,
					c1y: y0 - n_y0 - vy01,
					c2x: x0 + n_x0 - vx01,
					c2y: y0 + n_y0 - vy01,
					ex: x0 + n_x0,
					ey: y0 + n_y0
				})
				a[0].mx = a[0].mx.toFixed(1)
				a[0].mx = parseFloat(a[0].mx)
				a[0].my = a[0].my.toFixed(1)
				a[0].my = parseFloat(a[0].my)
				for (let i = 1; i < a.length; i++) {
					a[i].c1x = a[i].c1x.toFixed(1)
					a[i].c1x = parseFloat(a[i].c1x)
					a[i].c1y = a[i].c1y.toFixed(1)
					a[i].c1y = parseFloat(a[i].c1y)
					a[i].c2x = a[i].c2x.toFixed(1)
					a[i].c2x = parseFloat(a[i].c2x)
					a[i].c2y = a[i].c2y.toFixed(1)
					a[i].c2y = parseFloat(a[i].c2y)
					a[i].ex = a[i].ex.toFixed(1)
					a[i].ex = parseFloat(a[i].ex)
					a[i].ey = a[i].ey.toFixed(1)
					a[i].ey = parseFloat(a[i].ey)
				}
				return a
			},
			// 绘制贝塞尔曲线
			drawBethel(point, is_fill, color) {
				this.ctx.beginPath()
				this.ctx.moveTo(point[0].mx, point[0].my)
				if (color != undefined) {
					this.ctx.setFillStyle(color)
					this.ctx.setStrokeStyle(color)
				} else {
					this.ctx.setFillStyle(point[0].color)
					this.ctx.setStrokeStyle(point[0].color)
				}
				for (let i = 1; i < point.length; i++) {
					this.ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i]
						.ey)
				}
				this.ctx.stroke()
				if (is_fill != undefined) {
					//填充图形 ( 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序 )
					this.ctx.fill()
				}
				this.ctx.draw(true)
			},
			// 旋转图片
			async getRotateImage(dataUrl) {
				// const url = await this.base64ToPath(dataUrl)
				const url = dataUrl

				// 创建新画布
				const tempCtx = uni.createCanvasContext('temp-tn-sign-canvas', this)
				const width = this.canvasWidth
				const height = this.canvasHeight
				tempCtx.restore()
				tempCtx.save()
				tempCtx.translate(0, height)
				tempCtx.rotate(270 * Math.PI / 180)
				tempCtx.drawImage(url, 0, 0, width, height)
				tempCtx.draw()
				return new Promise((resolve, reject) => {
					setTimeout(() => {
						uni.canvasToTempFilePath({
							canvasId: 'temp-tn-sign-canvas',
							fileType: 'png',
							x: 0,
							y: height - width,
							width: height,
							height: width,
							success: res => resolve(res.tempFilePath),
							fail: reject
						}, this)
					}, 50)
				})
			},
			// 将base64转换为本地
			base64ToPath(dataUrl) {
				return new Promise((resolve, reject) => {
					// 判断地址是否包含bas64字样，不包含直接返回
					if (dataUrl.indexOf('base64') !== -1) {
						const data = uni.base64ToArrayBuffer(dataUrl.replace(/^data:image\/\w+;base64,/, ''))
						// #ifdef MP-WEIXIN
						const filePath =
							`${wx.env.USER_DATA_PATH}/${new Date().getTime()}-${Math.random().toString(32).slice(2)}.png`
						// #endif
						// #ifndef MP-WEIXIN
						const filePath = `${new Date().getTime()}-${Math.random().toString(32).slice(2)}.png`
						// #endif
						uni.getFileSystemManager().writeFile({
							filePath,
							data,
							encoding: 'base64',
							success: () => resolve(filePath),
							fail: reject
						})
					} else {
						resolve(dataUrl)
					}
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	.tn-sign-board {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		width: 100%;
		height: 100%;
		background-color: #E6E6E6;
		z-index: 997;
		display: flex;
		flex-direction: row-reverse;

		&__content {
			width: 84%;
			height: 100%;

			&__wrapper {
				width: calc(100% - 60rpx);
				height: calc(100% - 60rpx);
				margin: 30rpx;
				border-radius: 20rpx;
				border: 2rpx dotted #AAAAAA;
				overflow: hidden;
			}

			&__canvas {
				width: 100%;
				height: 100%;
				background-color: #FFFFFF;
			}
		}

		&__tools {
			width: 16%;
			height: 100%;
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: space-between;

			&__color {
				margin-top: 30rpx;

				&__item {
					width: 70rpx;
					height: 70rpx;
					border-radius: 100rpx;
					margin: 20rpx auto;

					&--active {
						position: relative;

						&::after {
							content: '';
							position: absolute;
							top: 50%;
							left: 50%;
							width: 40%;
							height: 40%;
							border-radius: 100rpx;
							background-color: #FFFFFF;
							transform: translate(-50%, -50%);
						}
					}
				}
			}

			&__button {
				margin-bottom: 30rpx;
				display: flex;
				flex-direction: column;

				&__item {
					width: 130rpx;
					height: 60rpx;
					line-height: 60rpx;
					text-align: center;
					margin: 60rpx auto;
					border-radius: 10rpx;
					color: #FFFFFF;
					transform-origin: center center;
					transform: rotateZ(90deg);
				}
			}
		}
	}
</style>